﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using Utility.PoolSystem;

namespace Utility.Events
{
	public static class EventPool
	{
		static Dictionary<Type, ISimplePool> pools = new Dictionary<Type, ISimplePool> ();

		static ISimplePool GetPool (Type eventType, bool createNew = false)
		{
			return createNew ? pools.GetOrNew (eventType, SimplePoolUtility.CreatePool) : pools.TryGetValue (eventType);
		}

		public static bool Despawn (EventBase e)
		{
			return GetPool (e.GetType ())?.Despawn (e) ?? false;
		}

		public static object Spawn (Type eventType)
		{
			return GetPool (eventType, true).Spawn ();
		}

		public static TEvent Spawn<TEvent> ()
		{
			return (TEvent)Spawn (typeof (TEvent));
		}
	}

	public abstract class EventBase : IDisposable, IPoolCallback
	{
		enum Operations
		{
			None,
			Add,
			Remove,
		}

		struct Operation
		{
			public Operations operation;
			public Delegate del;

			public Operation (Operations operation, Delegate del)
			{
				this.operation = operation;
				this.del = del;
			}
		}

		protected List<Delegate> listeners = new List<Delegate> ();
		protected bool isInvoking = false;
		List<Operation> queuedOps = new List<Operation> ();

		protected void _Add (Delegate listener, bool allowRepeat)
		{
			if (listener != null)
			{
				if (allowRepeat || !listeners.Contains (listener))
				{
					if (isInvoking)
					{
						queuedOps.Add (new Operation (Operations.Add, listener));
					}
					else
					{
						listeners.Add (listener);
					}
				}
			}
		}

		protected void _Remove (Delegate listener)
		{
			if (listener != null)
			{
				if (isInvoking)
				{
					queuedOps.Add (new Operation (Operations.Remove, listener));
				}
				else
				{
					listeners.Remove (listener);
				}
			}
		}

		protected virtual void PreInvoke ()
		{
			isInvoking = true;
		}

		protected virtual void PostInvoke ()
		{
			isInvoking = false;
			if (queuedOps.Count > 0)
			{
				foreach (var op in queuedOps)
				{
					switch (op.operation)
					{
						case Operations.Add:
							listeners.Add (op.del);
							break;
						case Operations.Remove:
							listeners.Remove (op.del);
							break;
					}
				}
				queuedOps.Clear ();
			}
		}

		public virtual void Clear ()
		{
			listeners.Clear ();
		}

		public virtual void Dispose ()
		{
			if (!Despawn ())
			{
				Clear ();
			}
		}

		public bool Despawn ()
		{
			return EventPool.Despawn (this);
		}

		protected virtual void OnSpawn ()
		{
			Clear ();
		}
		void IPoolCallback.OnSpawn () => OnSpawn ();

		protected virtual void OnDespawn ()
		{
			Clear ();
		}
		void IPoolCallback.OnDespawn () => OnDespawn ();
	}

	public class Event : EventBase
	{
		public static Event Spawn () => EventPool.Spawn<Event> ();

		public void Add (Action action, bool allowRepeat = false)
		{
			_Add (action, allowRepeat);
		}

		public void Remove (Action action)
		{
			_Remove (action);
		}

		public void Invoke ()
		{
			PreInvoke ();
			foreach (var a in listeners)
			{
				(a as Action).Invoke ();
			}
			PostInvoke ();
		}
	}

	public class Event<T0> : EventBase
	{
		public static Event<T0> Spawn () => EventPool.Spawn<Event<T0>> ();

		public void Add (Action action, bool allowRepeat = false)
		{
			_Add (action, allowRepeat);
		}

		public void Add (Action<T0> action, bool allowRepeat = false)
		{
			_Add (action, allowRepeat);
		}

		public void Remove (Action action)
		{
			_Remove (action);
		}

		public void Remove (Action<T0> action)
		{
			_Remove (action);
		}

		public void Invoke (T0 t0)
		{
			PreInvoke ();
			foreach (var a in listeners)
			{
				if (a is Action a0)
				{
					a0.Invoke ();
				}
				else if (a is Action<T0> a1)
				{
					a1.Invoke (t0);
				}
			}
			PostInvoke ();
		}
	}
}